/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package phd.dupenois.changeidentifiers.optimisation.singlepattern;

import java.io.File;
import phd.dupenois.changeidentifiers.optimisation.singlepattern.frames.SettingsDialog;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Random;
import java.util.Vector;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import phd.dupenois.changeidentifiers.ChangeIdentifier;
import phd.dupenois.changeidentifiers.ChangeIdentifierHolder;
import phd.dupenois.changeidentifiers.ChangeIdentifierListener;
import phd.dupenois.changeidentifiers.ChangeIdentifierSet;
import phd.dupenois.changeidentifiers.dotpatterns.DotPattern;
import phd.dupenois.changeidentifiers.engine.ChangeIdentifierApplication;
import phd.dupenois.changeidentifiers.engine.TerminatingRunEngine;
import phd.dupenois.changeidentifiers.engine.testing.Test;
import phd.dupenois.changeidentifiers.exceptions.ChangeIdentifierLoadException;
import phd.dupenois.changeidentifiers.exceptions.ChangeIdentifierSetNotValidException;
import phd.dupenois.changeidentifiers.footprints.Footprint;
import phd.dupenois.changeidentifiers.footprints.algorithms.FootprintAlgorithm;
import phd.dupenois.changeidentifiers.optimisation.graphs.OptimisationGraph;
import phd.dupenois.changeidentifiers.optimisation.singlepattern.frames.GraphFrame;
import phd.dupenois.changeidentifiers.optimisation.singlepattern.frames.ResultSaveInterface;

/**
 *
 * @author Max
 */
public class SinglePatternOld extends ChangeIdentifierApplication implements 
        Runnable, ChangeIdentifierListener, ResultSaveInterface {

//    private static String[][] identifiers = new String[][]{
//        new String[]{
//            "C:\\Users\\Max\\Development\\Java\\ChangeIdentifierProjects\\ChangeIdentifiersProjects\\CollectedChangeIdentifiers\\dist\\CollectedChangeIdentifiers.jar",
//            "phd.dupenois.changeidentifiers.areaboundingbox.AreaOfBoundingBox"
//        }
//    };
    private static String[][] identifiers = new String[][]{
        new String[]{
            "/secamfs/userspace/phd/mpd209/NetBeansProjects/ChangeIdentifierProjects/ChangeIdentifiersProjects/CollectedChangeIdentifiers/dist/CollectedChangeIdentifiers.jar",
            "phd.dupenois.changeidentifiers.areaboundingbox.AreaSymmetricDifferenceBoundingBox"
        }
    };
    private HashMap<ChangeIdentifierSet, double[]> precomputedResults;
//    private double minimumUnit = 0.1;
    private double newIdentifiersChance = 0.2;
//    private double newParentChance = 0.5;
    private int maxIdentifiers = 4;
    private int populationCount = 40;
    private int maxGeneration = 5000;
    private static int PAUSE = 500;
    private static int GRAPH_REDRAW = 10;
    private int generation;
    private TerminatingRunEngine ciEngine;
//    private OptimisationTestManager testManager;
    private int version = 0;
    private Random randomNewIdentifiers;
    private Random randomNoise;
    private Random randomNumberIdentifiers;
    private Random randomIdentifier;
    private Random randomPriority;
    private Random randomThreshold;
    private Random randomTotalThreshold;
    private Random randomRedrawOnFail;
    private Random randomMaxFails;
    private Random randomMultiplier;
    private Random randomSet;
    private Vector<ChangeIdentifierSet> currentPopulation;
    private Vector<DotPattern> patterns;
    private FootprintAlgorithm algorithm;
    private OptimisationGraph graph;
    private GraphFrame frame;
    public SinglePatternOld() {
        super(true);
        SettingsDialog settingsDialog = new SettingsDialog(null, true);
        settingsDialog.setVisible(true);
        String[] dotPatternLocations = settingsDialog.getDotPatternStream();
        patterns = new Vector<DotPattern>();
        for (String location : dotPatternLocations) {
            DotPattern dp = new DotPattern();
            try {
                dp.load(location);
                patterns.add(dp);
            } catch (FileNotFoundException ex) {
                exceptionThrown(ex);
                System.exit(1);
            } catch (IOException ex) {
                exceptionThrown(ex);
                System.exit(1);
            }
        }
        Class<? extends FootprintAlgorithm> algorithmClass = settingsDialog.getAlgorithmClass();
        try {
            algorithm = algorithmClass.newInstance();
        } catch (InstantiationException ex) {
            exceptionThrown(ex);
            System.exit(1);
        } catch (IllegalAccessException ex) {
            exceptionThrown(ex);
            System.exit(1);
        }
        graph = new OptimisationGraph();
        frame = new GraphFrame(graph, this);
        frame.setVisible(true);
        ciEngine = (TerminatingRunEngine) this.engine;
        randomNumberIdentifiers = new Random(System.currentTimeMillis());
        randomIdentifier = new Random(System.currentTimeMillis());
        randomPriority = new Random(System.currentTimeMillis());
        randomThreshold = new Random(System.currentTimeMillis());
        randomTotalThreshold = new Random(System.currentTimeMillis());
        randomRedrawOnFail = new Random(System.currentTimeMillis());
        randomMaxFails = new Random(System.currentTimeMillis());
        randomMultiplier = new Random(System.currentTimeMillis());
        randomNewIdentifiers = new Random(System.currentTimeMillis());
        randomNoise = new Random(System.currentTimeMillis());
        randomSet = new Random(System.currentTimeMillis());
//        randomNewParent = new Random(System.currentTimeMillis());
        precomputedResults = new HashMap<ChangeIdentifierSet, double[]>();
//        testManager = new OptimisationTestManager();
    }

    private ChangeIdentifierHolder randomIdentifier(int numberOfIdentifiers)
            throws ChangeIdentifierLoadException {
        int index = randomIdentifier.nextInt(identifiers.length);
        String[] details = identifiers[index];
        int priority = randomPriority.nextInt(numberOfIdentifiers);
        double threshold = randomThreshold.nextDouble() * 100;
        double multiplier = randomMultiplier.nextDouble();
        boolean redrawOnFail = randomRedrawOnFail.nextBoolean();
        return new ChangeIdentifierHolder(details[0], details[1], priority, threshold, redrawOnFail, multiplier);
    }

    private ChangeIdentifierSet createRandomSet() throws ChangeIdentifierLoadException {
        ChangeIdentifierSet changeIdentifierSet = new ChangeIdentifierSet();
        int numberOfIdentifiers = randomNumberIdentifiers.nextInt(maxIdentifiers) + 1;
        ChangeIdentifierHolder holder;
        for (int i = 0; i < numberOfIdentifiers; i++) {
//            System.out.println("Adding Identifier "+i);
            holder = randomIdentifier(numberOfIdentifiers);
            changeIdentifierSet.add(holder);
        }
        changeIdentifierSet.setConcurrent(false);
        double threshold = randomTotalThreshold.nextDouble() * 100;
        int maxFails = randomMaxFails.nextInt(numberOfIdentifiers) + 1;
        changeIdentifierSet.setMaxFails(maxFails);
        changeIdentifierSet.setTotalThreshold(threshold);
        version = 0;
        changeIdentifierSet.setVersion(getVersionString());
        changeIdentifierSet.setName("random-set-created-" + System.currentTimeMillis());
        changeIdentifierSet.setDescription("Random set created at " + System.currentTimeMillis());
//        System.out.println("Built Set ");
        
        return changeIdentifierSet;
    }

    private String getVersionString() {
        int length = (maxGeneration + "").length();
        StringBuffer current = new StringBuffer(""+version);
        while (current.length() < length) {
            current = current.insert(0, "0");
        }
        return current.insert(0, "1.").toString();
    }
    public void randomisePopulationOrder(){
        Collections.shuffle(currentPopulation);
    }
    public boolean isRedrawGraph(){
        return true;
//        if(generation==0) return true;
//        if(generation>=maxGeneration-1) return true;
//        return (generation%GRAPH_REDRAW==0);
    }
    public void run() {
        frame.setMaxGeneration(maxGeneration);
        try {
            currentPopulation = new Vector<ChangeIdentifierSet>();
            for (int i = 0; i < populationCount; i++) {
//                System.out.println("Creating random set "+i);
                getCurrentPopulation().add(createRandomSet());
            }
            for(ChangeIdentifierSet original : getCurrentPopulation()){
                double[] results = getResults(original);
                precomputedResults.put(original, results);
                graph.addPoint(results[0], results[1], original);
            }
            ChangeIdentifierSet mutant;
            Dominates fitnessResult;
            Iterator<ChangeIdentifierSet> setIter;
            Iterator<ChangeIdentifierSet> comparisonSetIter;
            ChangeIdentifierSet set;
            ChangeIdentifierSet comparison;
            Vector<ChangeIdentifierSet> mutants;
            Vector<ChangeIdentifierSet> newPopulation;
            double[] results;
            for (generation = 0; generation < maxGeneration && getCurrentPopulation().size() > 0; generation++) {
//                try{
//                    Thread.sleep(PAUSE);
//                }catch(InterruptedException ex){
//
//                }
                setIter = getCurrentPopulation().iterator();
                mutants = new Vector<ChangeIdentifierSet>();
                while (setIter.hasNext()) {
                    mutant = mutate(setIter.next());
                    mutants.add(mutant);
                    //Add mutants to graph
                    results = getResults(mutant);
                    graph.addPoint(results[0], results[1], mutant);
                }
                currentPopulation.addAll(mutants);
                randomisePopulationOrder();
                newPopulation = new Vector<ChangeIdentifierSet>();
                setIter = getCurrentPopulation().iterator();
                while (newPopulation.size()<populationCount&&setIter.hasNext()) {
                    set = setIter.next();
                    comparisonSetIter = getCurrentPopulation().iterator();
                    fitnessResult = null;
                    while ((fitnessResult==null||!fitnessResult.isDominated())
                            && comparisonSetIter.hasNext()) {
                        comparison = comparisonSetIter.next();
                        if(comparison==set||comparison.equals(set)) continue;
                        fitnessResult = dominates(set, comparison);
                    }
                    //If not dominated add it to new population then remove it
                    //from current
                    if(!fitnessResult.isDominated()){
                        newPopulation.add(set);
                        setIter.remove();
                    }
                }
                //if we haven't got enough change identifiers add random ones
                //from the set (second randomisation may not be necessary)
                //ONLY DO THIS IF NOT ON LAST GENERATION
                if(generation != (maxGeneration-1)){
                    randomisePopulationOrder();
                    setIter = getCurrentPopulation().iterator();
                    while(newPopulation.size()<populationCount){
                        newPopulation.add(setIter.next());
                        setIter.remove();
                    }
                }
                //Remove uneeded results from graph and storage
                for(ChangeIdentifierSet setToRemove : this.currentPopulation){
                    this.precomputedResults.remove(setToRemove);
                    this.graph.removePoint(setToRemove);
                }
                //set current population to new population
                this.currentPopulation = newPopulation;

                frame.incrementGeneration();
            }

            System.out.println("All Generations Finished");
            double[] result;
            Vector<OptimisationGraph.SetDetails> setDetails = new Vector<OptimisationGraph.SetDetails>();
            for(ChangeIdentifierSet finishSet : getCurrentPopulation()){
                result = getResults(finishSet);
                System.out.println("Finish Set "+finishSet.getName()+" "+finishSet.getVersion()+" Took "+
                        result[0]+"ms for an error of "+result[1]);
                System.out.println(finishSet.toXMLString());
                System.out.println("---------------");
                setDetails.add(new OptimisationGraph.SetDetails(finishSet,result[0],result[1]));
            }
            graph.setFinishedSet(setDetails.toArray(new OptimisationGraph.SetDetails[setDetails.size()]));
        } catch (ChangeIdentifierLoadException ex) {
            exceptionThrown(ex);
            System.exit(1);
        }
    }

    public Dominates dominates(ChangeIdentifierSet set, ChangeIdentifierSet comparison) {
        double comparisonTimeTaken;
        double comparisonError;
        double setTimeTaken;
        double setError;
        double[] setResults;
        double[] comparisonResults;
        Dominates result;
        setResults = getResults(set);
        comparisonResults = getResults(comparison);
        setTimeTaken = setResults[0];
        setError = setResults[1];
        comparisonTimeTaken = comparisonResults[0];
        comparisonError = comparisonResults[1];

        if  ((setTimeTaken < comparisonTimeTaken && setError <= comparisonError)||
            (setError < comparisonError && setTimeTaken <= comparisonTimeTaken)){
            result = Dominates.DOMINATES;
        }else if((comparisonTimeTaken < setTimeTaken && comparisonError <= setError)||
                (comparisonError < setError && comparisonTimeTaken <= setTimeTaken)){
            result = Dominates.DOMINATED;

        }else{
            result = Dominates.MUTUALLY_NON_DOMINATING;
        }

//        if (originalTimeTaken < mutatedTimeTaken && originalError <= mutatedError) {
//            result = Dominates.DOMINATES;
//        } else if (originalError < mutatedError && originalTimeTaken <= mutatedTimeTaken) {
//            result = Dominates.DOMINATES;
//        } else if (originalError < mutatedError && originalTimeTaken > mutatedTimeTaken) {
//            result = Dominates.MUTUALLY_NON_DOMINATING;
//        } else if (originalTimeTaken < mutatedTimeTaken && originalError > mutatedTimeTaken) {
//            result = Dominates.MUTUALLY_NON_DOMINATING;
//        } else if (originalTimeTaken == mutatedTimeTaken && originalError == mutatedTimeTaken) {
//            result = Dominates.MUTUALLY_NON_DOMINATING;
//        } else {
//            result = Dominates.DOMINATED;
//        }
        return result;
    }

    private double[] getResults(ChangeIdentifierSet set) {
        if (precomputedResults.containsKey(set)) return precomputedResults.get(set);
        double timeTaken = -1;
        double error = -1;
        String title = "Optimisation Running Test";
        title += "[" + generation + "]";
        try {
           if(!set.isInitialised())set.initialise(this);
        } catch (ChangeIdentifierSetNotValidException ex) {
            exceptionThrown(ex);
            System.exit(1);
        }
        Test test = ciEngine.runEngine(algorithm, set, patterns.toArray(new DotPattern[patterns.size()]),
                title, "Run by the optimisation engine", 0);

        timeTaken = test.getTimeTaken();

        error = SymmetricAreaDifferenceError.getAverageError(test);
        
        double[] result  = new double[]{timeTaken, error};

//        System.out.println("Set "+set.getName()+" "+set.getVersion()+" Took "+
//                        result[0]+"ms for an error of "+result[1]);
//        System.out.println("With Change Identifiers redrew "+
//                test.getRedrawCount()+"/"+
//                test.getRedrawCountWithoutChangeIdentifiers());
//        System.out.println("-----------");

        precomputedResults.put(set, result);
//        System.out.println("Set: "+set.getName()+" "+set.getVersion()+" took "+timeTaken+"ms at total error: "+error);
        return result;
    }

    private ChangeIdentifierSet getRandomSet(Vector<ChangeIdentifierSet> currentSets,
            ChangeIdentifierSet current) {
        if (currentSets.size() < 2) {
            return currentSets.get(0);
        }
        ChangeIdentifierSet newSet = null;
        while (newSet == null || newSet.equals(current)) {
            newSet = currentSets.get(randomSet.nextInt(currentSets.size()));
        }
        return newSet;
    }

    public ChangeIdentifierSet mutate(ChangeIdentifierSet original) throws ChangeIdentifierLoadException {
        //Chance to completely redo identifiers, otherwise just modify them
        boolean newIdentifiers = randomNewIdentifiers.nextDouble() < newIdentifiersChance;
//        if(newIdentifiers) System.out.println("Creating new random identifier");
        if (newIdentifiers) {
            return createRandomSet();
        } else {

            ChangeIdentifierSet mutatedChangeIdentifierSet = new ChangeIdentifierSet();
            mutatedChangeIdentifierSet.setTotalThreshold(addNoise(original.getTotalThreshold(),
                    100d, new double[]{0}));
            mutatedChangeIdentifierSet.setMaxFails(addNoise(original.getMaxFails(),
                    10, new int[]{1, original.getChangeIdentifierHolders().length}));

            version++;
//            System.out.println("Mutant Version "+version+" == "+getVersionString());

            mutatedChangeIdentifierSet.setName(original.getName());
            mutatedChangeIdentifierSet.setDescription(original.getDescription());
            mutatedChangeIdentifierSet.setVersion(getVersionString());
            for (ChangeIdentifierHolder holder : original.getChangeIdentifierHolders()) {
                mutatedChangeIdentifierSet.add(mutateChangeIdentifier(holder));
            }
            return mutatedChangeIdentifierSet;
        }

    }

    private ChangeIdentifierHolder mutateChangeIdentifier(ChangeIdentifierHolder original)
            throws ChangeIdentifierLoadException {
        int priority = addNoise(original.getPriority(), 10, new int[]{0});
        double threshold = addNoise(original.getThreshold(), 100d, new double[]{0});
        double multiplier = addNoise(original.getMultiplier(), 1d, new double[]{0});
        boolean redrawOnFail = randomRedrawOnFail.nextBoolean();
        return new ChangeIdentifierHolder(original.getLocation(), original.getClassname(),
                priority, threshold, redrawOnFail, multiplier);
    }

    private double addNoise(double val, double scale, double[] limits) {
        val += randomNoise.nextGaussian() * scale;
        if (limits != null && limits.length >= 1) {
            if (val < limits[0]) {
                val = limits[0];
            }
        }
        if (limits != null && limits.length == 2) {
            if (val > limits[1]) {
                val = limits[1];
            }
        }
        return val;
    }

    private int addNoise(int val, int scale, int[] limits) {
        val += ((int) Math.round(randomNoise.nextGaussian())) * scale;
        if (limits != null && limits.length >= 1) {
            if (val < limits[0]) {
                val = limits[0];
            }
        }
        if (limits != null && limits.length == 2) {
            if (val > limits[1]) {
                val = limits[1];
            }
        }
        return val;
    }

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        // TODO code application logic here
        try {
            UIManager.setLookAndFeel(
                UIManager.getSystemLookAndFeelClassName());
        }catch (UnsupportedLookAndFeelException e) {
        }catch (ClassNotFoundException e) {
        }catch (InstantiationException e) {
        }catch (IllegalAccessException e) {
        }
        new Thread(new SinglePatternOld()).start();
    }

    /**
     * @return the currentFront
     */
    public Vector<ChangeIdentifierSet> getCurrentPopulation() {
        return currentPopulation;
    }

    public void exceptionThrown(Exception ex) {
        System.out.println("[EXCEPTION] " + ex.getClass().getSimpleName() + " :: " + ex.getMessage());
    }

    public void dotPatternBufferIsEmpty() {
    }

    public void drawDotPattern(DotPattern dotPattern) {
    }

    public void drawFootprint(Footprint footprint) {
    }

    public void startedEngine(long time) {
    }

    public void finishedWithChangeIdentifiers(long timeTaken, int redrawCount) {
    }

    public void finishedEngine() {
    }

    public void startedRecordingTests() {
    }

    public void finishedRecordingTests() {
    }

    public void startedRunningTestsWithoutChangeIdentifiers(long time) {
    }

    public void finishedRunningTestsWithoutChangeIdentifiers(long timeTaken, int redrawCount, long difference, int redrawCountDifference) {
    }

    public void startedChangeIdentifierSet(long time) {
    }

    public void finishedChangeIdentifierSet(long timeTaken) {
    }

    public void changeIdentifierBrokenMaxFail(ChangeIdentifier identifier, int maxFails) {
    }

    public void changeIdentifierBrokenOwnThreshold(ChangeIdentifier identifier, double threshold, double value, boolean redraw) {
    }

    public boolean saveResults(File folderLocation) {
        return false;
    }

    public static enum Dominates {

        DOMINATES,
        DOMINATED,
        MUTUALLY_NON_DOMINATING;

        public boolean isDominates() {
            return this.equals(Dominates.DOMINATES);
        }

        public boolean isDominated() {
            return this.equals(Dominates.DOMINATED);
        }

        public boolean isMutuallyNonDominating() {
            return this.equals(Dominates.MUTUALLY_NON_DOMINATING);
        }
    }
}
